﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Kurogane.Compiler;
using System.IO;
using Kurogane.Buildin;

namespace Kurogane.Runtime {
	public class Engine {

		private Scope _BuildinScope = new Scope();
		private Scope _DefaultGlobal;

		public Engine() {
			var loader = new StandardLibraryLoader(_BuildinScope);
			loader.Load();
			_DefaultGlobal = new Scope(_BuildinScope);
		}

		/// <summary>
		/// 与えられたプログラムを実行する。
		/// </summary>
		/// <param name="code">プログラム</param>
		/// <returns>実行結果</returns>
		public object Execute(string code) {
			return Execute(code, _DefaultGlobal);
		}

		/// <summary>
		/// プログラムをスコープの元で実行する。
		/// </summary>
		/// <param name="code">プログラム</param>
		/// <param name="scope">スコープ</param>
		/// <returns>実行結果</returns>
		public object Execute(string code, Scope scope) {
			var parser = new Parser(new Lexer(new StringReader(code)));
			var program = parser.Parse();
			return ExecuteBlock(program, scope);
		}

		// ----- ----- ----- ----- private ----- ----- ----- -----

		private object ExecuteBlock(Block program, Scope scope) {
			object last = null;
			foreach (var sen in program.Contents) {
				if (sen is NormalSentence) {
					last = ExecuteNormalSentence((NormalSentence)sen, scope);
				}
				else if (sen is CaseSentence) {
					last = ExecuteCaseSentence((CaseSentence)sen, scope);
				}
			}
			return last;
		}

		private object ExecuteCaseSentence(CaseSentence caseSentence, Scope scope) {
			throw new NotImplementedException();
		}

		private object ExecuteNormalSentence(NormalSentence sen, Scope scope) {
			object last = null;
			foreach (var atom in sen.Contents)
				last = ExecuteAtom(atom, scope);
			return last;
		}

		private object ExecuteAtom(SentenceAtom secAtom, Scope scope) {
			if (secAtom is ActionAtom) {
				return ExecuteActionAtom((ActionAtom)secAtom, scope);
			}
			if (secAtom is AssignmentAtom) {
				return ExecuteAssignmentAtom((AssignmentAtom)secAtom, scope);
			}
			throw new NotImplementedException();
		}

		private object ExecuteAssignmentAtom(AssignmentAtom atom, Scope scope) {
			var value = ExecuteTarget(atom.Arguments["を"], scope);
			var name = ((ReferenceTarget)atom.Arguments["と"]).Name;
			scope.SetVariable(name, value);
			return value;
		}

		private object ExecuteActionAtom(ActionAtom atom, Scope scope) {
			var action = GetAction(atom.Action, scope);
			var pps = action.PostPositions;
			object[] args = new object[pps.Count];
			for (int i = 0; i < args.Length; i++) {
				args[i] = ExecuteTarget(atom.Arguments[pps[i]], scope);
			}
			return action.Delegate.DynamicInvoke(args);
		}

		private object ExecuteTarget(Target target, Scope scope) {
			if (target is LiteralTarget) {
				return ((LiteralTarget)target).Value;
			}
			if (target is ReferenceTarget) {
				return scope.GetVariable(((ReferenceTarget)target).Name);
			}
			if (target is BinaryOperation) {
				return ExecuteOperation((BinaryOperation)target, scope);
			}
			if (target is Proc) {
				return ExecuteProc((Proc)target, scope);
			}
			throw new NotImplementedException();
		}

		private object ExecuteOperation(BinaryOperation target, Scope scope) {
			var op = scope.GetVariable(target.Operation);
			var act = (Delegate)op;
			return act.DynamicInvoke(ExecuteTarget(target.Left, scope), ExecuteTarget(target.Right, scope));
		}

		private KrgnProc GetAction(Target target, Scope scope) {
			if (target is ReferenceTarget) {
				return (KrgnProc)scope.GetVariable(((ReferenceTarget)target).Name);
			}
			throw new NotImplementedException();
		}

		private KrgnProc ExecuteProc(Proc proc, Scope scope) {
			var innerScope = new Scope(scope);
			if (proc.Parameter.Count == 0) {
				var param = new string[0];
				Func<object> action = delegate() {
					return ExecuteBlock(proc.Block, innerScope);
				};
				return new KrgnProc(param, action);
			}
			if (proc.Parameter.Count == 1) {
				string name = null;
				string postPos = null;
				foreach (var pair in proc.Parameter) {
					postPos = pair.Key;
					name = pair.Value;
				}
				Func<object, object> action = o => {
					innerScope.SetVariable(name, o);
					return ExecuteBlock(proc.Block, innerScope);
				};
				return new KrgnProc(new[] { postPos }, action);
			}
			return null;
		}
	}
}
